#include <click/config.h>
#include <click/confparse.hh>
#include <sys/types.h>
#include "../standard/tee.hh"
#include "igmpclient.hh"
#include <clicknet/ip.h>
#include <clicknet/udp.h>
#include <clicknet/ether.h>
#include "membershipquerymessage.hh"
#include <click/packet.hh>
#include <click/bighashmap.cc>

#include <click/error.hh>

CLICK_DECLS

bool is_multicast(const IPAddress& ip){
	return (ip.addr() & htonl(0xF0000000)) == htonl(0xE0000000);
}


IGMPClient::IGMPClient(){};

IGMPClient::~IGMPClient(){};

int IGMPClient::configure(Vector<String>& conf, ErrorHandler* err){
	if(cp_va_kparse(conf, this, err,"SRC", cpkP+cpkM, cpIPAddress, &clientip, cpEnd) < 0) return -1;
	return 0;
}

Packet* IGMPClient::pull(int port){
	Packet* p = input(0).pull();
	return p;
}

void IGMPClient::push(int port, Packet* p){
	IPAddress src = p->dst_ip_anno();
	if(is_multicast(src)){ //Check if the src address is a multicast address
		MembershipQueryMessage* format = (MembershipQueryMessage*) (p->data());
		if(p->packet_type_anno() == 1){ //src == 224.0.0.1
			if(format->groupaddr == 0 && ntohs(format->nos) == 0){
				uint32_t resptime = format->maxrespcode;
				uint8_t exp = (resptime >> 4) << 5;
				uint8_t mant = format->maxrespcode << 4;
				if(format->maxrespcode >= 128) resptime = (mant | 0x10) << (exp + 3);
				if(!generalqtimer.scheduled()){ //Check if a General Query response is already scheduled
					generalqtimer.schedule_after_ms(resptime);
					//Send MembershipReportMessage in response to General Query
				}
				else{
					p->kill();
				}
			}
			else{
				p->kill(); //Invalid general query
			}
		}
		else{ //Group specific query
			HashMap<uint32_t, IGMPState*>::Pair* statepair = states.find_pair(format->groupaddr.s_addr);
			if (statepair)
				SendReportMessage(statepair->value); //Reply to query
		}

//########### Some test code ############
		//click_chatter("It is a multicast");
		//const IPAddress ipadress("224.0.0.65");
		//SendReportMessage(new IGMPState(ipadress.in_addr()));
		SocketPort* port = new SocketPort(65);
		IPAddress mipad("224.0.0.65");
		Vector<in_addr> sources;
		IPAddress ipad("192.168.1.105");
		sources.push_back(ipad.in_addr());
		ipmulticastlisten(port, mipad, INCLUDE, sources);

		SocketPort* port2 = new SocketPort(77);
		IPAddress mipad2("224.0.0.66");
		Vector<in_addr> sources2;
		IPAddress ipad2("192.168.2.113");
		sources2.push_back(ipad2.in_addr());
		ipmulticastlisten(port2, mipad2, INCLUDE, sources2);

		SendReportMessage(states);

//#####################################

	} else {
		if(p) {
			int n = noutputs(); //Get amount of outputs
			for (int i = 1; i < n-1; i++)
			if (Packet *q = p->clone()){
				output(i).push(q);
			}
			output(n-1).push(p);
		}
	}
}

int IGMPClient::join(const String &data, Element *element, void *thunk, ErrorHandler *errh){
	IGMPClient* client = dynamic_cast<IGMPClient*>(element);
	IPAddress multicast;
	SocketPort* socket = new SocketPort(0);
	if(cp_va_kparse(data, client, errh, "MULTICAST", cpkP+cpkM, cpIPAddress, &multicast, "SOCKET", cpkP+cpkM, cpInteger, *socket, cpEnd) < 0) return -1;
	Vector<in_addr> srcs;
	srcs.push_back(client->getClientIP());
	return client->ipmulticastlisten(socket, multicast, INCLUDE, srcs);
}

int IGMPClient::leave(const String &data, Element *element, void *thunk, ErrorHandler *errh){
	IGMPClient* client = dynamic_cast<IGMPClient*>(element);
	IPAddress multicast;
	SocketPort* socket = new SocketPort(0);
	if(cp_va_kparse(data, client, errh, "MULTICAST", cpkP+cpkM, cpIPAddress, &multicast, "SOCKET", cpkP+cpkM, cpInteger, *socket, cpEnd) < 0) return -1;
	Vector<in_addr> srcs;
	srcs.push_back(client->getClientIP());
	return client->ipmulticastlisten(socket, multicast, EXCLUDE, srcs);
}

void IGMPClient::add_handlers(){
	add_write_handler("join", join, (void *)0);
	add_write_handler("leave", leave, (void *)0);
}

int IGMPClient::ipmulticastlisten(SocketPort* socket, const in_addr& multicastaddr, const IGMPFilterMode& filtermode, const Vector<in_addr>& sourcelist){
	HashMap<uint32_t, Vector<SocketPort*> >::Pair* socketpair = sockets.find_pair(multicastaddr.s_addr);
	if(!socketpair){
		Vector<SocketPort*> newsockets;
		newsockets.push_back(socket);
		sockets.insert(multicastaddr.s_addr, newsockets);
	}
	else{
		Vector<SocketPort*> vecsockets = socketpair->value;
		bool exist = false;
		int i = 0;
		while((i < vecsockets.size()) && !exist){
			if(*vecsockets[i] == *socket) exist = true;
			i++;
		}
		if(!exist){
			vecsockets.push_back(socket);
			sockets.remove(multicastaddr.s_addr);
			sockets.insert(multicastaddr.s_addr, vecsockets);
		}
	}

	HashMap<uint32_t, IGMPState*>::Pair* statepair = states.find_pair(multicastaddr.s_addr);
	IGMPState* newstate = new IGMPState(multicastaddr, filtermode, sourcelist);
	if(!statepair){
		states.insert(multicastaddr.s_addr, newstate);
	}
	else{
		IGMPState* state = statepair->value;
		bool report = state->merge(*newstate);
		if(report) SendReportMessage(state); //Sending Membership Report Message if needed
		//states.remove(multicastaddr.s_addr); @comment: Is this necessary? I think not.
		//states.insert(multicastaddr.s_addr, state);
		delete newstate;
	}
	return 0;
}

in_addr IGMPClient::getClientIP() const{
	return clientip.in_addr();
}

void IGMPClient::run_timer(Timer* t){
	if(t == &generalqtimer){
		//Send response message to general query
		SendReportMessage(states);
	}
}

int IGMPClient::initialize(ErrorHandler * errh){
	generalqtimer.initialize(this);
	return 0;
}

void IGMPClient::SendReportMessage(IGMPState* state){
	uint32_t headroom = sizeof(click_ip)+sizeof(click_udp)+sizeof(click_ether);
	uint32_t length = sizeof(MembershipReportMessage) + state->getGroupRecordSize();
	uint32_t tailroom = 0;
	WritablePacket* newpacket = Packet::make(headroom, 0, length, tailroom);
	if(!newpacket){
		click_chatter("cannot make packet!");
	}
	memset(newpacket->data(), 0, newpacket->length()); //Initialize memory to 0 starting at newpacket->data() for length = newpacket->length()
	MembershipReportMessage* format = (MembershipReportMessage*) newpacket->data();

	format->type = 0x22;
	format->reserved8 = 0;
	format->reserved16 = 0;
	format->nogr = htons(1);
	state->copyGroupRecord(format->grouprecords);
	format->checksum = click_in_cksum((unsigned char *)format, length);
	output(0).push(newpacket);
}

void IGMPClient::SendReportMessage(const HashMap<uint32_t, IGMPState*>& states){
	uint32_t headroom = sizeof(click_ip)+sizeof(click_udp)+sizeof(click_ether);
	uint32_t length = sizeof(MembershipReportMessage);
	uint32_t tailroom = 0;
	for(HashMap<uint32_t, IGMPState*>::const_iterator cit = states.begin(); cit.live(); cit++){
		length +=  cit.value()->getGroupRecordSize();
	}

	WritablePacket* newpacket = Packet::make(headroom, 0, length, tailroom);
	if(!newpacket){
		click_chatter("cannot make packet!");
	}
	memset(newpacket->data(), 0, newpacket->length()); //Initialize memory to 0 starting at newpacket->data() for length = newpacket->length()
	MembershipReportMessage* format = (MembershipReportMessage*) newpacket->data();

	format->type = 0x22;
	format->reserved8 = 0;
	format->reserved16 = 0;
	format->nogr = htons(states.size());
	char* next = (char*) format->grouprecords;
	IGMPGroupRecord* nextgroup;
	for(HashMap<uint32_t, IGMPState*>::const_iterator cit = states.begin(); cit.live(); cit++){
		nextgroup = (IGMPGroupRecord*) next;
		cit.value()->copyGroupRecord(nextgroup);
		next += cit.value()->getGroupRecordSize();
	}
	format->checksum = click_in_cksum((unsigned char *)format, length);
	output(0).push(newpacket);
}

CLICK_ENDDECLS
ELEMENT_REQUIRES(userlevel|ns|vector|igmpstate)
EXPORT_ELEMENT(IGMPClient)
